package wxauth

import (
	"fmt"
	"net/http"
	"net/url"

	"gitee.com/bitwormhole/weixin-applet-backend/app/classes/wxusers"
	"github.com/starter-go/httpagent"
	"github.com/starter-go/rbac"
	"github.com/starter-go/security/auth"
)

// LoginMechanism ... 定义 微信小程序 登录机制的名称
const LoginMechanism = "weixin.applet"

// WeixinAppletAuthResult 。。。
type WeixinAppletAuthResult struct {
	OpenID     string `json:"openid"`      // "openid":"xxxxxx",
	SessionKey string `json:"session_key"` // "session_key":"xxxxx",
	UnionID    string `json:"unionid"`     // "unionid":"xxxxx",
	ErrCode    int    `json:"errcode"`     // "errcode":0,
	ErrMsg     string `json:"errmsg"`      // "errmsg":"xxxxx"
}

////////////////////////////////////////////////////////////////////////////////

// WeixinAppletAuth ...
type WeixinAppletAuth struct {

	//starter:component

	_as func(auth.Registry) //starter:as(".")

	WebClients    httpagent.Clients //starter:inject("#")
	WxUserService wxusers.Service   //starter:inject("#")

	AppKeyID     string //starter:inject("${weixin.applet.app-id}")
	AppKeySecret string //starter:inject("${weixin.applet.app-secret}")

}

func (inst *WeixinAppletAuth) _impl() (auth.Authenticator, auth.Registry) {
	return inst, inst
}

// ListRegistrations ...
func (inst *WeixinAppletAuth) ListRegistrations() []*auth.Registration {

	r1 := &auth.Registration{
		Enabled:       true,
		Priority:      0,
		Authenticator: inst,
		Mechanism:     inst,
	}

	return []*auth.Registration{r1}
}

// Support ...
func (inst *WeixinAppletAuth) Support(r auth.Request) bool {
	a1, ok := r.(auth.Authentication)
	if !ok {
		return false
	}
	return a1.Mechanism() == LoginMechanism
}

// Authenticate ...
func (inst *WeixinAppletAuth) Authenticate(a auth.Authentication) ([]auth.Identity, error) {

	res, err := inst.checkByWeixinAPI(a)
	if err != nil {
		return nil, err
	}

	if res.ErrCode != 0 {
		const f = "wx.api return ErrCode:%d ErrMsg:%s"
		return nil, fmt.Errorf(f, res.ErrCode, res.ErrMsg)
	}

	ctx := a.Context()
	openid := res.OpenID
	o1 := &wxusers.DTO{OpenID: openid}
	ser := inst.WxUserService
	if ser.HasOpenID(ctx, openid) {
		// do find
		o2, err := ser.FindByOpenID(ctx, openid)
		if err != nil {
			return nil, err
		}
		o1 = o2
	} else {
		// do sign-up
		o2, err := ser.SignUp(ctx, o1)
		if err != nil {
			return nil, err
		}
		o1 = o2
	}

	// do login
	o3, err := ser.SignIn(ctx, o1)
	if err != nil {
		return nil, err
	}

	return inst.makeAuthResult(a, o3)
}

func (inst *WeixinAppletAuth) makeAuthResult(a auth.Authentication, user *rbac.UserDTO) ([]auth.Identity, error) {
	ident := auth.NewUserIdentity(a, user)
	list := []auth.Identity{ident}
	return list, nil
}

func (inst *WeixinAppletAuth) checkByWeixinAPI(a auth.Authentication) (*WeixinAppletAuthResult, error) {

	const base = "https://api.weixin.qq.com/sns/jscode2session"

	u, err := url.Parse(base)
	if err != nil {
		return nil, err
	}

	code := a.Account()
	appid := inst.AppKeyID
	secret := inst.AppKeySecret
	granttype := "authorization_code"

	q := u.Query()
	q.Set("js_code", code)
	q.Set("appid", appid)
	q.Set("secret", secret)
	q.Set("grant_type", granttype)
	u.RawQuery = q.Encode()

	req := &httpagent.Request{
		Method: http.MethodGet,
		URL:    u.String(),
	}

	client := inst.WebClients.GetClient()
	resp, err := client.Execute(req)
	if err != nil {
		return nil, err
	}

	status := resp.Status
	if status != http.StatusOK {
		return nil, fmt.Errorf("HTTP %s", resp.Message)
	}

	ent, err := resp.GetEntity()
	if err != nil {
		return nil, err
	}

	result := &WeixinAppletAuthResult{}
	err = ent.ReadJSON(result)
	if err != nil {
		return nil, err
	}

	return result, nil
}
